package com.baijia.authentication;

import com.baijia.authentication.handler.AuthenticationHandler;
import com.baijia.authentication.handler.exception.AuthenticationException;
import com.baijia.authentication.handler.exception.BadCredentialsAuthenticationException;
import com.baijia.authentication.handler.exception.UnsupportedCredentialsException;
import com.baijia.authentication.principal.Credentials;
import com.baijia.authentication.principal.CredentialsToPrincipalResolver;
import com.baijia.authentication.principal.Principal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * @title AuthenticationManagerImpl
 * @desc 认证流程实现类
 * @author caoliang
 * @date 2015年12月4日
 * @version 1.0
 */
@Component
public final class AuthenticationManagerImpl extends AbstractAuthenticationManager {

    @NotNull
    @Size(min = 1)
    @Autowired
    private List<AuthenticationHandler> authenticationHandlers;

    @NotNull
    @Size(min = 1)
    @Autowired
    private List<CredentialsToPrincipalResolver> credentialsToPrincipalResolvers;

    @Override
    protected Pair<AuthenticationHandler, Principal> authenticateAndObtainPrincipal(final Credentials credentials)
        throws AuthenticationException {
        boolean foundSupported = false;
        boolean authenticated = false;

        AuthenticationHandler authenticatedClass = null;
        String handlerName = null;

        Exception finalException = BadCredentialsAuthenticationException.ERROR;
        for (final AuthenticationHandler authenticationHandler : this.authenticationHandlers) {
            if (authenticationHandler.supports(credentials)) {
                foundSupported = true;
                handlerName = authenticationHandler.getClass().getName();
                try {
                    if (!authenticationHandler.authenticate(credentials)) {
                        logger.info("{} failed to authenticate {}", handlerName, credentials);
                    } else {
                        logger.info("{} successfully authenticated {}", handlerName, credentials);
                        authenticatedClass = authenticationHandler;
                        authenticated = true;
                        break;
                    }
                } catch (final Exception e) {
                    finalException = e;
                    // handleError(handlerName, credentials, e);
                }
            }
        }

        if (!authenticated) {
            if (foundSupported) {
                logger.info("authenticationHandler handle error credentials : {}", credentials);
                handleError(handlerName, credentials, finalException);
                throw BadCredentialsAuthenticationException.ERROR;
            }

            logger.info("no authenticationHandler support credentials:{}", credentials);
            throw UnsupportedCredentialsException.ERROR;
        }

        foundSupported = false;
        for (final CredentialsToPrincipalResolver credentialsToPrincipalResolver : this.credentialsToPrincipalResolvers) {
            if (credentialsToPrincipalResolver.supports(credentials)) {
                final Principal principal = credentialsToPrincipalResolver.resolvePrincipal(credentials);
                logger.info("Resolved principal : {}", principal);
                foundSupported = true;
                if (principal != null) {
                    return new Pair<AuthenticationHandler, Principal>(authenticatedClass, principal);
                }
            }
        }

        if (foundSupported) {
            logger.info("CredentialsToPrincipalResolver found but no principal returned.credentials : {}", credentials);
            throw BadCredentialsAuthenticationException.ERROR;
        }

        logger.error("CredentialsToPrincipalResolver not found for {}", credentials.getClass().getName());
        throw UnsupportedCredentialsException.ERROR;
    }

    public void setAuthenticationHandlers(final List<AuthenticationHandler> authenticationHandlers) {
        this.authenticationHandlers = authenticationHandlers;
    }

    public void setCredentialsToPrincipalResolvers(
        final List<CredentialsToPrincipalResolver> credentialsToPrincipalResolvers) {
        this.credentialsToPrincipalResolvers = credentialsToPrincipalResolvers;
    }

}